﻿//  --------------------------------
//  Copyright (c) AW Systems, Inc. All rights reserved.
//  This source code is made available under the terms of the Microsoft Public License (Ms-PL)
//  http://www.codeplex.com/SLAM/license
//  ---------------------------------

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Linq;
using System.Text;
using AWS.SLAM.Configuration;
using AWS.SLAM.Data;
using AWS.SLAM.Events;

namespace AWS.SLAM.Data
{
    public class SqlDataServices : IDataServices
    {
        #region Private State

        /// <summary>
        /// Internal repository/data access layer 
        /// </summary>
        private SqlDataRepository _database;

        /// <summary>
        /// SLAM configuration defining the data model
        /// </summary>
        private IConfigurationManager _configurationManager;
        private string _dataSchema;

        #endregion

        #region Constructors

        /// <summary>
        /// Creates a new instance of DataServices for the given configuration.
        /// </summary>
        /// <param name="configuration"></param>
        public SqlDataServices(IConfigurationManager configuration)
        {
            _configurationManager = configuration;

            string connectionString = "";
            if (_configurationManager.DataMapping is XmlSqlDataMapping)
            {
                connectionString = ((XmlSqlDataMapping)_configurationManager.DataMapping).GetConnectionString();
            }

            if (String.IsNullOrEmpty(connectionString))
            {
                connectionString = _configurationManager.GetSection<ConnectionStringSection>("ConnectionStrings").Default;
            }
            else
            {
                connectionString = _configurationManager.GetSection<ConnectionStringSection>("ConnectionStrings").GetConnectionString(connectionString);
            }

            _database = new SqlDataRepository(connectionString);
        }

        ~SqlDataServices()
        {
            _database.Dispose();
        }

        #endregion

        #region IDataServices Members

        /// <summary>
        /// Creates a new row in the association table.
        /// </summary>
        /// <param name="associationTypeName"></param>
        /// <param name="firstTypeName"></param>
        /// <param name="firstId"></param>
        /// <param name="secondTypeName"></param>
        /// <param name="secondId"></param>
        /// <returns></returns>
        public bool Associate(string associationTypeName, string firstTypeName, string firstId, string secondTypeName, string secondId)
        {
            EnsureDataSchema();
            _database.EnsureDatabaseExists();
            if (!_database.VerifyTableExists(_dataSchema, GetTableName(associationTypeName)))
            {
                _database.CreateAssociationTable(_dataSchema, GetTableName(associationTypeName), GetTableName(firstTypeName), GetTableName(secondTypeName));
            }

            _database.InsertAssociation(_dataSchema, GetTableName(associationTypeName), firstTypeName, firstId, secondTypeName, secondId);

            return true;
        }

        public bool Associate(string associationTypeName, string firstTypeName, string firstId, string secondTypeName, string secondId, params object[] extendedParameters)
        {
            EnsureDataSchema();
            return Associate(associationTypeName, firstTypeName, firstId, secondTypeName, secondId);
        }

        /// <summary>
        /// Deletes a row in the table for the typeName with the given id
        /// </summary>
        /// <param name="typeName"></param>
        /// <param name="id"></param>
        /// <returns></returns>
        public bool Delete(string typeName, string id)
        {
            EnsureDataSchema();
            _database.EnsureDatabaseExists();
            if (_database.VerifyTableExists(_dataSchema, GetTableName(typeName)))
            {
                _database.DeleteItem(_dataSchema, GetTableName(typeName), id);
            }
            else
            {
                _database.CreateContentTable(_dataSchema, GetTableName(typeName), _configurationManager.DataMapping.GetTypeFields("", typeName) as IEnumerable<Field>);
            }
            return true;
        }

        public bool Delete(string typeName, string id, params object[] extendedParameters)
        {
            EnsureDataSchema();
            return Delete(typeName, id);
        }

        /// <summary>
        /// Deletes all associations of the given association type for the given type and id.
        /// </summary>
        /// <param name="associationTypeName"></param>
        /// <param name="typeName"></param>
        /// <param name="id"></param>
        /// <returns></returns>
        public bool DeleteAllAssociations(string associationTypeName, string typeName, string id)
        {
            EnsureDataSchema();
            _database.EnsureDatabaseExists();
            if (_database.VerifyTableExists(_dataSchema, GetTableName(associationTypeName)))
            {
                _database.DeleteAllAssociations(_dataSchema, GetTableName(associationTypeName), GetTableName(typeName), id);
            }
            return true;
        }

        public bool DeleteAllAssociations(string associationTypeName, string typeName, string id, params object[] extendedParameters)
        {
            EnsureDataSchema();
            return DeleteAllAssociations(associationTypeName, typeName, id);
        }

        /// <summary>
        /// Delete the association of the given association type for the given ids.
        /// </summary>
        /// <param name="associationTypeName"></param>
        /// <param name="firstTypeName"></param>
        /// <param name="firstId"></param>
        /// <param name="secondTypeName"></param>
        /// <param name="secondId"></param>
        /// <returns></returns>
        public bool DeleteAssociation(string associationTypeName, string firstTypeName, string firstId, string secondTypeName, string secondId)
        {
            EnsureDataSchema();
            _database.EnsureDatabaseExists();
            if (_database.VerifyTableExists(_dataSchema, GetTableName(associationTypeName)))
            {
                _database.DeleteAssociation(_dataSchema, GetTableName(associationTypeName), GetTableName(firstTypeName), firstId, GetTableName(secondTypeName), secondId);
            }
            return true;
        }

        public bool DeleteAssociation(string associationTypeName, string firstTypeName, string firstId, string secondTypeName, string secondId, params object[] extendedParameters)
        {
            EnsureDataSchema();
            return DeleteAssociation(associationTypeName, firstTypeName, firstId, secondTypeName, secondId);
        }

        /// <summary>
        /// Inserts a new row into the table for the typeName with the Hashtable of field names and values.
        /// </summary>
        /// <param name="typeName"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public bool Insert(string typeName, Hashtable values)
        {
            EnsureDataSchema();
            _database.EnsureDatabaseExists();

            if (!_database.VerifyTableExists(_dataSchema, GetTableName(typeName)))
            {
                _database.CreateContentTable(_dataSchema, GetTableName(typeName), _configurationManager.DataMapping.GetTypeFields("", typeName) as IEnumerable<Field>);
            }
            else
            {
                _database.UpdateContentTable(_dataSchema, GetTableName(typeName), _configurationManager.DataMapping.GetTypeFields("", typeName) as IEnumerable<Field>);
            }

            _database.InsertItem(_dataSchema, GetTableName(typeName), values);

            return true;
        }

        public bool Insert(string typeName, Hashtable values, params object[] extendedParameters)
        {
            EnsureDataSchema();
            return Insert(typeName, values);
        }

        /// <summary>
        /// Updates a row in the table for the typeName of the given with id with the Hashtable of field names and values.
        /// </summary>
        /// <param name="typeName"></param>
        /// <param name="id"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public bool Update(string typeName, string id, Hashtable values)
        {
            EnsureDataSchema();
            _database.EnsureDatabaseExists();
            if (!_database.VerifyTableExists(_dataSchema, GetTableName(typeName)))
            {
                _database.CreateContentTable(_dataSchema, GetTableName(typeName), _configurationManager.DataMapping.GetTypeFields("", typeName) as IEnumerable<Field>);
            }
            else
            {
                _database.UpdateContentTable(_dataSchema, GetTableName(typeName), _configurationManager.DataMapping.GetTypeFields("", typeName) as IEnumerable<Field>);
            }

            if (!_database.UpdateItem(_dataSchema, GetTableName(typeName), id, values))
            {
                _database.InsertItem(_dataSchema, GetTableName(typeName), values);
            }

            return true;
        }

        public bool Update(string typeName, string id, Hashtable values, params object[] extendedParameters)
        {
            EnsureDataSchema();
            return Update(typeName, id, values);
        }

        #endregion

        private void EnsureDataSchema()
        {
            if (String.IsNullOrEmpty(_dataSchema))
            {
                _dataSchema = ((XmlSqlDataMapping)_configurationManager.DataMapping).GetDataSchema();

                if (!_database.VerifySchemaExists(_dataSchema))
                {
                    _database.CreateSchema(_dataSchema);
                }
            }
        }

        private string GetTableName(string typeName)
        {
            return Regex.Replace(typeName, "[\\W\\s]", "");
        }
    }
}
